package com.ai.ly.goodsautocat.isphone;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.ai.ly.goodsautocat.autocat.DictMapOfAutoCat;
import com.ai.ly.goodsautocat.common.Constants;
import com.ai.ly.goodsautocat.common.SampleFactory;
import com.ai.ly.goodsautocat.common.SampleInfoBuilderFactory;
import com.ai.ly.goodsautocat.util.FileUtil;
import com.ai.ly.goodsautocat.vo.Fscore;
import com.ai.ly.goodsautocat.vo.PredictSampleVO;
import com.ai.ly.goodsautocat.vo.SampleVO;
import com.ai.ly.goodsautocat.vo.TrainningSampleVO;

import de.bwaldvogel.liblinear.Feature;
import de.bwaldvogel.liblinear.FeatureNode;
import de.bwaldvogel.liblinear.Linear;
import de.bwaldvogel.liblinear.Model;
import de.bwaldvogel.liblinear.Parameter;
import de.bwaldvogel.liblinear.Problem;
import de.bwaldvogel.liblinear.SolverType;

/**
 * 
 * Title: 使用SVM的线性核进行训练 <br>
 * Description: <br>
 * Date: 2018年4月4日 <br>
 * Copyright (c) 2018 AILK <br>
 * 
 * @author liangyi
 */
public class TrainIsPhoneWithLibLinear {
    
    /**最多训练多少个样本*/
    static final int MAX_SAMPLE_NUMBER=Integer.MAX_VALUE;
    
    
    static String modelFilePath="data\\sample(isphone)\\model";
    
    public Model model;
    
    public Problem problem = new Problem();

    public static void main(String[] args) throws Exception {
        trainModel();
        
        String gdsName="梁毅";
        predict(gdsName);
    }
    
    /**
     * 训练模型
     * @throws Exception
     * @author liangyi
     */
    public static void trainModel() throws Exception {
        TrainIsPhoneWithLibLinear train=new TrainIsPhoneWithLibLinear();
        //开始训练
        train.train();
        //保存model
        train.saveModel();
        //对训练样本进行测试，看看准确率
        System.out.println("开始使用训练样本进行验证");
        double[] predictY=train.predictY(train.problem.x);
        Fscore fscore=FscoreBuilder.buildFscore(train.problem.y, predictY);
        System.out.println("使用训练样本进行验证，得到结果如下：");
        System.out.println(fscore);
    }
    
    /**
     * 使用训练好的模型进行预测
     * @param gdsName 商品名称
     * @return
     * @throws Exception
     * @author liangyi
     */
    public static double predict(String gdsName) throws Exception {
        //根据商品名称生成样本
        PredictSampleVO sample =SampleFactory.buildPredictSample(gdsName,SampleInfoBuilderFactory.getBuilderForIsphone());
        if (sample==null) {
            System.out.println("这个商品名称不在字典中，不能进行预测...");
            return Integer.MIN_VALUE;
        }
        
        TrainIsPhoneWithLibLinear train=new TrainIsPhoneWithLibLinear();
        train.loadModel();
        Feature[] features=train.buildSvmNodes(sample);
        double predict=train.predictY(features);
        System.out.println("预测结果="+predict);
        return predict;
    }
    
    public double predictY(Feature[] features) {
        double prediction = Linear.predict(model, features);
        return prediction;
    }
    
    public double[] predictY(Feature[][] arrayOfFeatures) {
        double[] result=new double[arrayOfFeatures.length];
        int i=0;
        for (Feature[] features : arrayOfFeatures) {
            double prediction = predictY(features);
            result[i]=prediction;
            i++;
        }
        return result;
    }
    
    /**
     * 把模型保存到文件中
     * @throws Exception
     * @author liangyi
     */
    public void saveModel() throws Exception {
        File modelFile=FileUtil.getFileInSys(modelFilePath);
        model.save(modelFile);
    }
    
    /**
     * 把文件中的模型加载到内存
     * @throws Exception
     * @author liangyi
     */
    public void loadModel() throws Exception {
        File modelFile=FileUtil.getFileInSys(modelFilePath);
        this.model=Model.load(modelFile);
    }
    
    /**
     * 使用样本进行训练
     * @return
     * @author liangyi
     */
    public Model train() {
        long t1=System.currentTimeMillis();
        //加载样本到内存中
        Map<String, TrainningSampleVO> sampleMap = loadTrainSampleMap();
        //将样本封装成svm所需的格式
        Problem prob=buildSvmProblem(sampleMap);
        this.problem=prob;
        long t2=System.currentTimeMillis();
        System.out.println("加载样本完毕，用时:"+(t2-t1)/1000+"秒");
        sampleMap=null;
        
        //开始训练--------------------------
        System.out.println("开始训练样本");
        t1=System.currentTimeMillis();
        //训练的参数
        SolverType solver = SolverType.L2R_LR; // -s 0
        double C = 1;    // cost of constraints violation
        double eps = 0.01; // stopping criteria
        Parameter parameter = new Parameter(solver, C, eps);
        Model model = Linear.train(problem, parameter);
        t2=System.currentTimeMillis();
        System.out.println("训练样本完毕，用时:"+(t2-t1)/1000+"秒");
        
        this.model=model;
        return model;
    }

    /**
     * 加载样本数据
     * @return
     * @author liangyi
     */
    public Map<String, TrainningSampleVO> loadTrainSampleMap() {
        System.out.println("开始加载样本");
        
        //读取样本文件获取所有样本信息-----------------------------------------------------------
        File orgSampleFile=FileUtil.getFileInSys(Constants.orgSampleFilePath);
        Map<String, TrainningSampleVO> sampleMap=new HashMap<>();
        try {
            BufferedReader e = new BufferedReader(new FileReader(orgSampleFile));
            for (String lineContent = e.readLine(); lineContent != null; lineContent = e.readLine()) {
                //解析每行数据
                int lastIndex=lineContent.lastIndexOf(",");
                if (lastIndex<=0) {
                    continue;
                }
                
                String gdsName=lineContent.substring(0, lastIndex);
                //商品分类
                String catId=lineContent.substring(lastIndex+1, lineContent.length());
                
                TrainningSampleVO sample=SampleFactory.buildTrainningSample(gdsName, catId,SampleInfoBuilderFactory.getBuilderForIsphone());
                if (sample==null) {
                    continue;
                }
                
                //要过滤掉相同的样本(样本相同的话，sample中yesIndexMap、y的内容必定是相同的)
                sampleMap.put(sample.getIndexCountAndYStr(), sample);
                
                if (sampleMap.size()>=MAX_SAMPLE_NUMBER) {
                    break;
                }
            }
            e.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //读取样本文件获取所有样本信息end-----------------------------------------------------------
        System.out.println("从样本文件中获取到了"+sampleMap.size()+"个有效的样本");
        return sampleMap;
    }
    
    public Problem buildSvmProblem(Map<String, TrainningSampleVO> sampleMap) {
        //打印多少个正样本，多少个负样本
        int posSampleCount=0;
        int negSampleCount=0;
        //将样本封装成svm所需的格式
        Problem prob=new Problem();
        int xLength=DictMapOfAutoCat.getDictSize();
        /**训练数据的数目*/
        int l=sampleMap.size();
        /**包含其目标值的数组（分类时是整数，回归时是实数）*/
        double[] y=new double[l];
        /**一个指针的数组，每一个指针指向一个训练向量的离散表示（svm_node的数组）*/
        Feature[][] X=new FeatureNode[l][];
        
        //生成训练数据
        int index=0;
        for (Map.Entry<String, TrainningSampleVO> entry : sampleMap.entrySet()) {
            TrainningSampleVO sample=entry.getValue();
            
            Feature[] nodeArr=buildSvmNodes(sample);
            
            X[index]=nodeArr;
            y[index]=sample.getY();
            
            index++;
            
            if (sample.getY()>0) {
                posSampleCount++;
            }else {
                negSampleCount++;
            }
        }
        prob.l=l;
        prob.n=xLength;//bias=0，所以n=特征数
        prob.x=X;
        prob.y=y;
        
        System.out.println("加载了"+prob.l+"个样本，其中正样本"+posSampleCount+"，负样本"+negSampleCount);
        
        return prob;
    }
    
    public FeatureNode[] buildSvmNodes(SampleVO sample) {
        Map<Integer, Integer> indexCountMap=sample.getIndecCountMap();
        
        //FeatureNode的index要从小到大排序
        FeatureNode[] nodeArr=new FeatureNode[indexCountMap.size()]; 
        
        int i=0;
        Set<Integer> keySet = indexCountMap.keySet();
        Iterator<Integer> iter = keySet.iterator();
        while (iter.hasNext()) {
            int index=iter.next();
            int value=indexCountMap.get(index);
            
            FeatureNode node=new FeatureNode(index, value);
            nodeArr[i]=node;
            i++;
        }
        return nodeArr;
    }
    
}
